package edu.northwestern.cbits.purple_robot_manager.probes.services; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.os.Bundle; import android.preference.CheckBoxPreference; import android.preference.Preference; import android.preference.Preference.OnPreferenceClickListener; import android.preference.PreferenceManager; import android.preference.PreferenceScreen; import android.widget.Toast; import org.apache.commons.io.IOUtils; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.Map; import edu.northwestern.cbits.purple_robot_manager.EncryptionManager; import edu.northwestern.cbits.purple_robot_manager.R; import edu.northwestern.cbits.purple_robot_manager.activities.settings.FlexibleListPreference; import edu.northwestern.cbits.purple_robot_manager.logging.LogManager; import edu.northwestern.cbits.purple_robot_manager.logging.SanityCheck; import edu.northwestern.cbits.purple_robot_manager.logging.SanityManager; import edu.northwestern.cbits.purple_robot_manager.probes.Probe; import edu.northwestern.cbits.purple_robot_manager.probes.builtin.LocationProbe; import edu.northwestern.cbits.xsi.oauth.FoursquareApi; import edu.northwestern.cbits.xsi.oauth.Keystore; import edu.northwestern.cbits.xsi.oauth.OAuthActivity; public class FoursquareProbe extends Probe { private static final boolean DEFAULT_ENABLED = false; private static final String OAUTH_TOKEN = "oauth_foursquare_token"; private static final String OAUTH_SECRET = "oauth_foursquare_secret"; private static final String ENABLED = "config_probe_foursquare_enabled"; private static final String FREQUENCY = "config_probe_foursquare_frequency"; private static final String PULL_ACTIVITY = "config_probe_foursquare_pull_activity"; private static final boolean DEFAULT_PULL_ACTIVITY = false; private static final String MOST_RECENT = "config_foursquare_most_recent"; private long _lastCheck = 0; @Override public String getPreferenceKey() { return "services_foursquare"; } @Override public String summary(Context context) { return context.getString(R.string.summary_foursquare_probe_desc); } @Override public String probeCategory(Context context) { return context.getResources().getString(R.string.probe_external_services_category); } @Override public String name(Context context) { return "edu.northwestern.cbits.purple_robot_manager.probes.services.FoursquareProbe"; } @Override public String title(Context context) { return context.getString(R.string.title_foursquare_probe); } @Override public void enable(Context context) { SharedPreferences prefs = Probe.getPreferences(context); Editor e = prefs.edit(); e.putBoolean(FoursquareProbe.ENABLED, true); e.commit(); } @Override public void disable(Context context) { SharedPreferences prefs = Probe.getPreferences(context); Editor e = prefs.edit(); e.putBoolean(FoursquareProbe.ENABLED, false); e.commit(); } private void initKeystore(Context context) { Keystore.put(FoursquareApi.CONSUMER_KEY, context.getString(R.string.foursquare_consumer_key)); Keystore.put(FoursquareApi.CONSUMER_SECRET, context.getString(R.string.foursquare_consumer_secret)); Keystore.put(FoursquareApi.CALLBACK_URL, "http://purple.robot.com/oauth/foursquare"); } public boolean isEnabled(final Context context) { final SharedPreferences prefs = Probe.getPreferences(context); if (super.isEnabled(context)) { final long now = System.currentTimeMillis(); if (prefs.getBoolean(FoursquareProbe.ENABLED, FoursquareProbe.DEFAULT_ENABLED)) { synchronized (this) { this.initKeystore(context); long freq = Long.parseLong(prefs.getString(FoursquareProbe.FREQUENCY, Probe.DEFAULT_FREQUENCY)); if (now - this._lastCheck > freq) { final FoursquareProbe me = this; final String token = prefs.getString(FoursquareProbe.OAUTH_TOKEN, null); final String secret = prefs.getString(FoursquareProbe.OAUTH_SECRET, null); final String title = context.getString(R.string.title_foursquare_check); final SanityManager sanity = SanityManager.getInstance(context); if (token == null || secret == null) { String message = context.getString(R.string.message_foursquare_check); Runnable action = new Runnable() { @Override public void run() { me.fetchAuth(context); } }; sanity.addAlert(SanityCheck.WARNING, title, message, action); } else { sanity.clearAlert(title); Keystore.put(FoursquareApi.USER_TOKEN, token); Keystore.put(FoursquareApi.USER_SECRET, secret); Runnable r = new Runnable() { @Override public void run() { try { long mostRecent = prefs.getLong(FoursquareProbe.MOST_RECENT, 0); long newRecent = 0; JSONObject checkins = FoursquareApi.fetch("https://api.foursquare.com/v2/users/self/checkins?limit=250&sort=newestfirst&v=20130815"); if (checkins.has("response")) { JSONArray items = checkins.getJSONObject("response").getJSONObject("checkins").getJSONArray("items"); for (int i = items.length() - 1; i >= 0; i--) { JSONObject item = items.getJSONObject(i); long created = item.getLong("createdAt") * 1000; if (created > mostRecent) { if (created > newRecent) newRecent = created; Bundle eventBundle = new Bundle(); eventBundle.putString(Probe.BUNDLE_PROBE, me.name(context)); eventBundle.putLong(Probe.BUNDLE_TIMESTAMP, created / 1000); eventBundle.putString("CHECKIN_SOURCE", item.getJSONObject("source").getString("name")); if (item.has("venue")) { JSONObject venue = item.getJSONObject("venue"); JSONObject location = venue.getJSONObject("location"); eventBundle.putDouble("LATITUDE", location.getDouble("lat")); eventBundle.putDouble("LONGITUDE", location.getDouble("lng")); if (location.has("address")) eventBundle.putString("ADDRESS", location.getString("address")); if (location.has("city")) eventBundle.putString("CITY", location.getString("city")); if (location.has("state")) eventBundle.putString("STATE", location.getString("state")); if (location.has("postalCode")) eventBundle.putString("POSTAL_CODE", location.getString("postalCode")); eventBundle.putString("COUNTRY", location.getString("cc")); eventBundle.putString("VENUE_ID", venue.getString("id")); if (venue.has("url")) eventBundle.putString("VENUE_URL", venue.getString("url")); eventBundle.putString("VENUE_NAME", venue.getString("name")); JSONArray categories = venue.getJSONArray("categories"); for (int j = 0; j < categories.length(); j++) { JSONObject category = categories.getJSONObject(j); if (category.getBoolean("primary")) eventBundle.putString("VENUE_CATEGORY", category.getString("name")); } try { // Sleep so initial load is delivered in order... if (i == 0) Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } me.transmitData(context, eventBundle); } } } if (newRecent > 0) { Editor e = prefs.edit(); e.putLong(FoursquareProbe.MOST_RECENT, newRecent); e.commit(); } } } catch (NullPointerException | JSONException | IllegalStateException e) { LogManager.getInstance(context).logException(e); } } }; Thread t = new Thread(r); t.start(); } me._lastCheck = now; } } return true; } } return false; } private void fetchAuth(Context context) { String userId = EncryptionManager.getInstance().getUserHash(context); Intent intent = new Intent(context, OAuthActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra(OAuthActivity.CONSUMER_KEY, context.getString(R.string.foursquare_consumer_key)); intent.putExtra(OAuthActivity.CONSUMER_SECRET, context.getString(R.string.foursquare_consumer_secret)); intent.putExtra(OAuthActivity.REQUESTER, "foursquare"); intent.putExtra(OAuthActivity.CALLBACK_URL, "http://purple.robot.com/oauth/foursquare"); intent.putExtra(OAuthActivity.LOG_URL, LogManager.getInstance(context).getLogUrl(context)); intent.putExtra(OAuthActivity.HASH_SECRET, userId); context.startActivity(intent); } @Override public Map<String, Object> configuration(Context context) { Map<String, Object> map = super.configuration(context); SharedPreferences prefs = Probe.getPreferences(context); long freq = Long.parseLong(prefs.getString(FoursquareProbe.FREQUENCY, Probe.DEFAULT_FREQUENCY)); map.put(Probe.PROBE_FREQUENCY, freq); boolean pullActivity = prefs.getBoolean(FoursquareProbe.PULL_ACTIVITY, FoursquareProbe.DEFAULT_PULL_ACTIVITY); map.put(FoursquareProbe.PULL_ACTIVITY, pullActivity); return map; } @Override public void updateFromMap(Context context, Map<String, Object> params) { super.updateFromMap(context, params); if (params.containsKey(Probe.PROBE_FREQUENCY)) { Object frequency = params.get(Probe.PROBE_FREQUENCY); if (frequency instanceof Double) { frequency = ((Double) frequency).longValue(); } if (frequency instanceof Long) { SharedPreferences prefs = Probe.getPreferences(context); Editor e = prefs.edit(); e.putString(FoursquareProbe.FREQUENCY, frequency.toString()); e.commit(); } } if (params.containsKey(FoursquareProbe.PULL_ACTIVITY)) { Object pull = params.get(FoursquareProbe.PULL_ACTIVITY); if (pull instanceof Boolean) { Boolean pullBoolean = (Boolean) pull; SharedPreferences prefs = Probe.getPreferences(context); Editor e = prefs.edit(); e.putBoolean(FoursquareProbe.PULL_ACTIVITY, pullBoolean); e.commit(); } } } @Override @SuppressWarnings("deprecation") public PreferenceScreen preferenceScreen(final Context context, PreferenceManager manager) { final PreferenceScreen screen = super.preferenceScreen(context, manager); screen.setTitle(this.title(context)); screen.setSummary(R.string.summary_foursquare_probe_desc); final SharedPreferences prefs = Probe.getPreferences(context); String token = prefs.getString(FoursquareProbe.OAUTH_TOKEN, null); String secret = prefs.getString(FoursquareProbe.OAUTH_SECRET, null); CheckBoxPreference enabled = new CheckBoxPreference(context); enabled.setTitle(R.string.title_enable_probe); enabled.setKey(FoursquareProbe.ENABLED); enabled.setDefaultValue(FoursquareProbe.DEFAULT_ENABLED); screen.addPreference(enabled); FlexibleListPreference duration = new FlexibleListPreference(context); duration.setKey(FoursquareProbe.FREQUENCY); duration.setEntryValues(R.array.probe_low_frequency_values); duration.setEntries(R.array.probe_low_frequency_labels); duration.setTitle(R.string.probe_frequency_label); duration.setDefaultValue(Probe.DEFAULT_FREQUENCY); screen.addPreference(duration); CheckBoxPreference pull = new CheckBoxPreference(context); pull.setKey(FoursquareProbe.PULL_ACTIVITY); pull.setDefaultValue(FoursquareProbe.DEFAULT_PULL_ACTIVITY); pull.setTitle(R.string.config_probe_foursquare_pull_title); pull.setSummary(R.string.config_probe_foursquare_pull_summary); screen.addPreference(pull); final Preference authPreference = new Preference(context); authPreference.setTitle(R.string.title_authenticate_foursquare_probe); authPreference.setSummary(R.string.summary_authenticate_foursquare_probe); final FoursquareProbe me = this; final Preference logoutPreference = new Preference(context); logoutPreference.setTitle(R.string.title_logout_foursquare_probe); logoutPreference.setSummary(R.string.summary_logout_foursquare_probe); authPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() { @Override public boolean onPreferenceClick(Preference preference) { me.fetchAuth(context); screen.addPreference(logoutPreference); screen.removePreference(authPreference); return true; } }); logoutPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() { @Override public boolean onPreferenceClick(Preference preference) { Editor e = prefs.edit(); e.remove(FoursquareProbe.OAUTH_TOKEN); e.remove(FoursquareProbe.OAUTH_SECRET); e.remove(FoursquareProbe.MOST_RECENT); e.commit(); screen.addPreference(authPreference); screen.removePreference(logoutPreference); if (context instanceof Activity) { Activity activity = (Activity) context; activity.runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(context, context.getString(R.string.toast_foursquare_logout), Toast.LENGTH_LONG).show(); } }); } return true; } }); if (token == null || secret == null) screen.addPreference(authPreference); else screen.addPreference(logoutPreference); return screen; } @Override public JSONObject fetchSettings(Context context) { JSONObject settings = super.fetchSettings(context); try { JSONArray values = new JSONArray(); values.put(true); values.put(false); JSONObject encrypt = new JSONObject(); encrypt.put(Probe.PROBE_TYPE, Probe.PROBE_TYPE_BOOLEAN); encrypt.put(Probe.PROBE_VALUES, values); settings.put(FoursquareProbe.PULL_ACTIVITY, encrypt); JSONObject frequency = new JSONObject(); frequency.put(Probe.PROBE_TYPE, Probe.PROBE_TYPE_LONG); values = new JSONArray(); String[] options = context.getResources().getStringArray(R.array.probe_low_frequency_values); for (String option : options) { values.put(Long.parseLong(option)); } frequency.put(Probe.PROBE_VALUES, values); settings.put(Probe.PROBE_FREQUENCY, frequency); } catch (JSONException e) { LogManager.getInstance(context).logException(e); } return settings; } // TODO: Decide whether to keep or change this method... public static void annotate(Context context, Bundle bundle) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); if (prefs.getBoolean(FoursquareProbe.ENABLED, FoursquareProbe.DEFAULT_ENABLED)) { String urlString = "https://api.foursquare.com/v2/venues/search?client_id=" + context.getString(R.string.foursquare_consumer_key) + "&client_secret=" + context.getString(R.string.foursquare_consumer_secret) + "&v=20130815&ll="; double latitude = bundle.getDouble(LocationProbe.LATITUDE); double longitude = bundle.getDouble(LocationProbe.LONGITUDE); urlString += latitude + "," + longitude; try { URL u = new URL(urlString); InputStream in = u.openStream(); String jsonString = IOUtils.toString(in); in.close(); JSONObject respJson = new JSONObject(jsonString); JSONArray venues = respJson.getJSONObject("response").getJSONArray("venues"); if (venues.length() > 0) { JSONObject venue = venues.getJSONObject(0); bundle.putString("FOURSQUARE_NAME", venue.getString("name")); JSONObject location = venue.getJSONObject("location"); if (location.has("address")) bundle.putString("FOURSQUARE_ADDRESS", location.getString("address")); if (location.has("city")) bundle.putString("FOURSQUARE_CITY", location.getString("city")); if (location.has("state")) bundle.putString("FOURSQUARE_STATE", location.getString("state")); if (location.has("postalCode")) bundle.putString("FOURSQUARE_POSTAL_CODE", location.getString("postalCode")); bundle.putString("FOURSQUARE_COUNTRY", location.getString("cc")); bundle.putString("FOURSQUARE_VENUE_ID", venue.getString("id")); if (venue.has("url")) bundle.putString("FOURSQUARE_VENUE_URL", venue.getString("url")); JSONArray categories = venue.getJSONArray("categories"); String[] venueCategories = new String[categories.length()]; for (int j = 0; j < categories.length(); j++) { JSONObject category = categories.getJSONObject(j); venueCategories[j] = category.getString("name"); if (category.getBoolean("primary")) bundle.putString("FOURSQUARE_VENUE_CATEGORY", category.getString("name")); } bundle.putStringArray("FOURSQUARE_VENUE_CATEGORIES", venueCategories); } } catch (JSONException | IOException e) { LogManager.getInstance(context).logException(e); } } } @Override public String summarizeValue(Context context, Bundle bundle) { String venue = bundle.getString("VENUE_NAME"); return context.getString(R.string.summary_foursquare_checkin_summary, venue); } }